//---------------------------------------
var express = require('express');
var logger = require('morgan');
const { StatusCodes } = require('http-status-codes');
const cors = require('cors');
const fs = require('fs-extra');
var path = require('path')
const UPLOAD_DIR = path.resolve(__dirname, 'upload')
const TEMP_DIR = path.resolve(__dirname, 'temp')
const CHUNK_SIZE = 100 * 1024 * 1024;
//----------------------------------------
var createError = require('http-errors');
var cookieParser = require('cookie-parser');
const multiparty = require('multiparty')

// var indexRouter = require('./routes/index');
// var usersRouter = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

app.use(cors());
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(express.static(path.resolve(__dirname, 'upload')));
//存放上传并合并好的文件
fs.ensureDirSync(UPLOAD_DIR);
//存放分片文件
fs.ensureDirSync(TEMP_DIR);
// app.use('/', indexRouter);
// app.use('/users', usersRouter);


app.post(`/upload/:filename`, async (req, res, next) => {
  //通过路径参数获取文件名
  const { filename } = req.params;
  console.log('filename: ' + filename);
  //通过查询参数获取分片名
  const { chunkFileName } = req.query
  console.log('chunkFileName: ' + chunkFileName);
  //写入文件的起始位置
  const start = isNaN(req.query.start) ? 0 : parseInt(req.query.start,10)
  //保存此文件分片的目录
  const chunkDir = path.resolve(TEMP_DIR, filename)
  //分片的文件路径
  const chunkFilePath = path.resolve(chunkDir, chunkFileName)
  //先确定分片目录存在
  await fs.ensureDir(chunkDir)
  //创建此文件的可写流
  const ws = fs.createWriteStream(chunkFilePath, {start,flags:'a'})
  //后面实现暂停操作，如果客户点击暂停按钮，取消上传操作，
  //取消后在服务器触发请求对象的aborted事件，关闭可定流
  req.on('aborted', () => { ws.close() })
  //使用管道的方式把请求中的请求体流数据写入文件中
  try {
    await pipeStream(req, ws)
    res.json({ success: true });
  } catch (error) {
    next(error)
  }
})
function pipeStream(rs, ws) {
  return new Promise((resolve, reject) => {
    //把可读流中的数据写入可写流中
    rs.pipe(ws).on('finish', resolve).on('error', reject)
  })
}
app.get(`/merge/:filename`, async (req, res, next) => {
  //通过路径参数获取文件名
  const { filename } = req.params;
  // console.log('filename: ' + filename);
  try {
    await mergeChunks(filename)
    res.json({ success: true });
  } catch (error) {
    next(error)
  }
})
async function mergeChunks(filename) {
  const mergedFilePath = path.resolve(UPLOAD_DIR, filename)
  const chunkDir = path.resolve(TEMP_DIR, filename)
  const chunkFiles = await fs.readdir(chunkDir) //读取目录
  //对分片按照索引进行升序排序
  chunkFiles.sort((a, b) => Number(a.split('-')[1]) - Number(b.split('-')[1]))
  try {
    //为了提高性能，实现并行写入
    const pipes = chunkFiles.map((chunkFile, index) => {
      return pipeStream(
        fs.createReadStream(path.resolve(chunkDir, chunkFile), { autoClose: true }),
        fs.createWriteStream(mergedFilePath, { start: index * CHUNK_SIZE })
      )
    })
    await Promise.all(pipes) //并行执行管道操作,写入目标文件
    fs.rmdir(chunkDir, { recursive: true }) // 删除分片目录
  } catch (error) {
    next(error)
  }
}


app.get(`/verify/:filename`, async (req, res, next) => {
  const { filename } = req.params
  //先获取文件在服务器的路径
  const filePath = path.resolve(UPLOAD_DIR, filename)
  //判断文件在服务器是否存在
  const isExist = await fs.pathExists(filePath) 
  //如果已经存在，则直接返回不需要上传
  if (isExist) {
    return res.json({success: true,needUpload:false})
  }
  //判断分片目录是否存在
  const chunkDir = path.resolve(TEMP_DIR, filename)
  const existDir = await fs.pathExists(chunkDir)
  //存放已将上传的分片的对象数组
  let uploadedChunkList = []
  if (existDir) {
    //读取临时目录里所有的分片对应的文件
    const chunkFileNames = await fs.readdir(chunkDir)
    //读取每个分片文件的文件信息，主要是文件大小，表示已经上传的文件的大小
    uploadedChunkList = await Promise.all(chunkFileNames.map(async function(chunkFileName){
      //fs.stat返回文件信息
      const {size} = await fs.stat(path.resolve(chunkDir, chunkFileName))
      return{chunkFileName,size}
    }))
  }
  //如果不存在，则返回需要上传
  //如果上传尚未完成，已上传部分，将已经上传的分片名和大小返回给客户端
  //客户端只需上传剩下的数据
  res.json({success:true,needUpload:true,uploadedChunkList})
})
//图片上传
// app.post('/upload', (req, res) => {
//   const form = new multiparty.Form()
//   form.uploadDir = './upload'
//   form.parse(req, (err, fields, fiels) => {
//       res.send({
//         path: 'http://127.0.0.1:3000/upload/' + fiels.file[0].path.split('\\').pop() 
//       })
//   })
// })


// catch 404 and forward to error handler
app.use(function (req, res, next) {
  next(createError(404));
});

// error handler
app.use(function (err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;
